package com.ijtunes;

import com.ijtunes.utils.Property;
import com.ijtunes.utils.Properties;
import com.ijtunes.filetypes.Track;
import com.ijtunes.utils.Logger;
import java.util.HashMap;
import java.util.logging.Level;

/**
 * Lists different tags that can be replaced in a file naming expression.
 * @author Michael Sena
 * @version 1.0
 * @date 02/07/2010
 */
public enum OrganisingTag {
    /** Album track appears on. */
    ALBUM("%album%", "Album the track appears on"),
    /** Overall / grouping artist of the album. */
    ALBUMARTIST("%albumartist%", "Album the track appears on"),
    /** Track artist. */
    ARTIST("%artist%", "Artist of the track"),
    /** Extension of the file. */
    EXTENSION("%extension%", "File extension"),
    /** Track title/name. */
    NAME("%name%", "Song name / title"),
    /** Root folder tracks should be stored under. */
    ROOT("%root%", "Root folder a track should be located under"),
    /** Track number (single digit if < 10). */
    TRACKNUM("%tracknum%", "Track number on the album"),
    /** Track number (double digit if < 10). */
    TRACKNUM2("%tracknum2%", "Track number on the album (minimum 2 digits");

    /**
     * Default constructor.
     * @param tagTag Name of the tag
     * @param tagDescription Description of the tag
     */
    private OrganisingTag(final String tagTag, final String tagDescription) {
        this.tag = tagTag;
        this.description = tagDescription;
    }

    /** Name of the tag. */
    private String tag = null;
    /** Description of the tag. */
    private String description = null;
    /** Static hashed data of artist and albums that have been parsed. */
    private static HashMap<String, String> hashedData = null;

    static {
        hashedData = new HashMap<String, String>();
    }

    @Override
    public String toString() {
        return tag;
    }

    /**
     * Converts a string to a tag.
     * @param tag String version of a tag
     * @return Object version of a tag
     */
    public static OrganisingTag tagFromString(final String tag) {
        String tempTag = tag.substring(1, tag.length() - 1).toUpperCase();
        try {
            return valueOf(tempTag);
        } catch (IllegalArgumentException iae) {
            Logger.log(OrganisingTag.class.getName(),
                "Invalid organising tag: " + tag,
                iae);
            return null;
        }
    }

    /**
     * Text-based description of a tag.
     * @return String version of the tag's description.
     */
    public String getDescription() {
        return description;
    }

    /**
     * Parses a tag and returns the value from a track's data.
     * @param tag Tag to obtain from the track
     * @param track Track/data source
     * @return Result from the track
     */
    public static String valueOfTag(OrganisingTag tag, final Track track) {
        String toReturn = null;

        if (tag.equals(OrganisingTag.ALBUMARTIST)) {
            toReturn = getStoreHash(track.getAlbumArtist());
            if (toReturn == null || toReturn.equals(Track.DEFAULT_BLANK)
                && Properties.getBool(Property.USE_ARTIST_IF_BLANK)) {
                tag = OrganisingTag.ALBUM;
            }
        }
        
        if (tag.equals(OrganisingTag.ALBUM)) {
            toReturn = getStoreHash(track.getAlbum());
        } else if (tag.equals(OrganisingTag.ARTIST)) {
            toReturn = getStoreHash(track.getArtist());
        } else if (tag.equals(OrganisingTag.EXTENSION)) {
            toReturn = track.getExtension();
        } else if (tag.equals(OrganisingTag.NAME)) {
            toReturn = parseTag(track.getName());
        } else if (tag.equals(OrganisingTag.ROOT)) {
            toReturn = Properties.get(Property.ORGANISING_ROOT);
        } else if (tag.equals(OrganisingTag.TRACKNUM)) {
            toReturn = Integer.toString(track.getTrackNum());
        } else if (tag.equals(OrganisingTag.TRACKNUM2)) {
            toReturn = toTwoDigits(track.getTrackNum());
        }

        if (toReturn == null || toReturn.equals(Track.DEFAULT_BLANK)) {
            Logger.log(OrganisingTag.class.getName(),
                "Tag field '" + tag + "' is blank for: " + track.toString(),
                Level.WARNING);
                return null;
        }

        return toReturn;
    }

    /**
     * Get a tag's previously parsed result from the hash table.•
     * Parse it if it hasn't been done yet.
     * @param original Original artist name, album etc.
     * @return Data from the hash value
     */
    private static String getStoreHash(final String original) {

        if (hashedData.containsKey(original)) {
            return hashedData.get(original);
        } else {
            String toReturn = parseTag(original);
            hashedData.put(original, toReturn);
            return toReturn;
        }
    }

    /**
     * Parses a tag with various options.
     * @param value Value to parse
     * @return Parsed data
     */
    private static String parseTag(final String value) {
        // TODO: Implement a converting to ASCII only
        String toReturn = replaceWinChars(value);
        return toReturn;
    }

    /**
     * Ensures a string does not contain invalid Windows folder characters.
     * @param value Value to check/convert
     * @return Parsed result
     */
    private static String replaceWinChars(final String value) {
        // Method 1: Found through profiler to be slower
        //toReturn = toReturn.replaceAll("[\\:|\\*|\\?|\\\"|\\<|\\>|\\|]", "_");

        // Method 2
        char[] tempChar = value.toCharArray();
        for (int i = 0; i < tempChar.length; i++) {
            if (tempChar[i] == ':'
                || tempChar[i] == '*'
                || tempChar[i] == '?'
                || tempChar[i] == '"'
                || tempChar[i] == '\\'
                || tempChar[i] == '/'
                || tempChar[i] == '|') {
                tempChar[i] = '_';
            }
        }

        return String.copyValueOf(tempChar);
    }

    /**
     * Converts a single digit into a double digit.
     * @param num Number to convert
     * @return At least a double digit track number
     */
    private static String toTwoDigits(final int num) {
        String number = Integer.toString(num);
        if (number.length() < 2) {
            number = "0" + number;
        }
        return number;
    }
}
